const Validator = require('./validator')
/**
 * 获取Parse对象的对应属性
 * @param sources {object} Parse对象
 * @param attributes {array} 需要获取的属性列表
 * @returns {{}}
 */
const mapAttributes = (sources, attributes) => {
  const obj = {}
  attributes.forEach((key) => {
    obj[key] = sources.get(key)
  })
  return obj
}
/**
 * 设置对象对应的键值
 * @param obj {object} 需要设置的对象
 * @param keyString {string} 需要设置的key
 * @param val 需要设置的值
 */
const setValByKey = (obj, keyString, val) => {
  const keyArray = keyString.split('.') // 分解key,将字符串转换为数组
  const len = keyArray.length
  let current = obj  // 当前需要设置值的对象

  if (len > 0) {
    keyArray.forEach((key, index) => {
      if (typeof current[key] === 'undefined') {  // 如果属性不存在
        if (index < len - 1) {  // 如果不是最后一个属性,则设置为空对象
          current[key] = {}
        } else {  // 否则,设置值
          current[key] = val
        }
      } else if (index + 1 === len) {  // 如果属性原来已经定义,在最后一个属性设置值
        current[key] = val
      }
      current = current[key]  // 更新当前需要设置值的对象
    })
  }
}

/**
 * 获取parse object 深层属性
 * @param parseObj {object} parse 对象
 * @param key {string} keys, 如key1.key2.key3
 * @param defaultVal {*} 当属性不存在时，默认的返回值
 * @returns {*}
 */
const getParseObjectDeep = (parseObj, key, defaultVal = undefined) => {
  const keys = key.split('.')
  let temp = parseObj
  for (let i = 0, len = keys.length; i < len; i++) {
    if (temp) {
      if (temp[keys[i]]) {
        temp = temp[keys[i]]
      } else {
        try {
          temp = temp.get(keys[i])
        } catch (err) {
          temp = temp[keys[i]]
        }
      }
    } else {
      break
    }
  }
  return typeof temp === 'undefined' ? defaultVal : temp
}
/**
 * 获取parseObj 自定义映射
 * @param parseObj {object} parse 对象
 * @param keyMap {array || object} 键映射
 * @returns {{}}
 */
const getAttributesMap = (parseObj, keyMap) => {
  const obj = {}
  let attributes = keyMap
  if (Array.isArray(keyMap)) {
    attributes = keyMap.reduce((prev, cur) => {
      prev[cur] = cur
      return prev
    }, {})
  }
  Object.keys(attributes).forEach((key) => {
    setValByKey(obj, key, getParseObjectDeep(parseObj, attributes[key]))
  })
  obj.id = parseObj.id
  return obj
}
/**
 * 获取 parse 数组的属性
 * @param parseArr {array} parse 对象数组
 * @param keyMap {array || object}
 * @param fn
 */
const mapArrayAttributes = (parseArr, keyMap, fn) => {
  return parseArr.map((item, index) => {
    const result = getAttributesMap(item, keyMap)
    return fn ? fn(result, item, index) : result
  })
}

/**
 * 批量设置parseObj的值，当值不为undefined时才设置
 * @param parseObj {object} parse object
 * @param tasks {object} 需要设置的key value值
 */
const setParseObjectValues = (parseObj, tasks) => {
  Object.keys(tasks).forEach((key) => {
    const value = tasks[key]
    if (typeof value !== 'undefined') {
      const keys = key.split('.')
      const target = keys.pop()
      const ret = keys.join('.')
      const obj = ret ? getParseObjectDeep(parseObj, ret) : parseObj
      obj.set(target, value)
    }
  })
}

/**
 * 正则
 * @type {{mobile: RegExp}}
 */
const patterns = {
  mobile: /^1[3|4|5|7|8][0-9]\d{8}$/  // 电话正则
}

/**
 * 分页查询
 */
class QueryPage {
  constructor (schema, pageSize = 15, currentPage = 1, where, rules) {
    this.pageSize = pageSize
    this.currentPage = currentPage
    this.where = where
    this.schema = schema
    this.query = new Parse.Query(schema)
    this.total = 0
    this.data = []
    this.rules = rules
  }

  /**
   * 对参数进行校检
   */
  validate () {
    const validator = new Validator(this.rules)
    return validator.validate(this.where).errors
  }

  /**
   * 获取当前query实例
   * @returns {*|Parse.Query}
   */
  getQuery () {
    return this.query
  }

  /**
   * 获取新的query实例
   * @returns {Parse.Query}
   */
  getNewQuery () {
    return new Parse.Query(this.schema)
  }

  /**
   * 设置query实例
   * @param query
   */
  setQuery (query) {
    this.query = query
  }

  /**
   * 获取结果
   * @param attributes {object}
   * @param fn {function}
   * @returns {Promise.<{code: number, total: (number|*), totalPage: number, currentPage: (number|*), pageSize: (number|*), data: (*|Array)}>}
   */
  getResult (attributes, fn) {
    if (Array.isArray(attributes)) {
      const temp = {}
      attributes.forEach((item) => {
        temp[item] = item
      })
      attributes = temp
    }
    const data = attributes && typeof attributes === 'object' ? mapArrayAttributes(this.data, attributes, fn) : this.data
    return {
      code: 1,
      total: this.total,   // 总数量
      totalPage: Math.ceil(this.total / this.pageSize),  // 总页数
      currentPage: this.currentPage,   // 当前页
      pageSize: this.pageSize,   // 每页数量
      data
    }
  }

  /**
   * 执行查询
   * @param attributes {object}
   * @param fn {function}
   * @returns {Promise.<{code: number, total: (number|*), totalPage: number, currentPage: (number|*), pageSize: (number|*), data: (*|Array)}>}
   */
  async fetch (attributes, fn, options) {
    if (this.rules) {
      const errors = this.validate()
      throw new Error(errors[0])
    }
    this.query.limit(this.pageSize).skip((this.currentPage - 1) * this.pageSize)
    const find = this.query.find(options)
    const count = this.query.count(options)
    this.data = await find
    this.total = await count
    return this.getResult(attributes, fn)
  }
}


/**
 * 异步map 方法
 * @param arr {array}
 * @param fn {function} 回调函数
 * @param concurrency {number} 并发数
 * @returns {Promise}
 */
const promiseMap = (arr, fn, concurrency) => {
  const len = arr.length
  concurrency = concurrency || len
  return new Promise((resolve, reject) => {
    let completed = 0
    let started = 0
    let running = 0
    const results = [];
    (function replenish () {
      if (completed >= len) {
        return resolve(results)
      }
      while (running < concurrency && started < len) {
        running++
        started++
        const index = started - 1
        fn.call(arr[index], arr[index], index).then((result) => {
          running--
          completed++
          results[index] = result
          replenish()
        }).catch(reject)
      }
    })()
  })
}

module.exports = {
  mapAttributes,
  setValByKey,
  getParseObjectDeep,
  getAttributesMap,
  mapArrayAttributes,
  setParseObjectValues,
  patterns,
  QueryPage,
  promiseMap
}
